/*
 * Copyright (c) 2022, IMMORTA Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * - Neither the name of IMMORTA Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef DMA_DRV_H
#define DMA_DRV_H

/*!
 * @file dma_drv.h
 * @brief This file declares dma driver interfaces
 */

/*!
 * @addtogroup dma_drv
 * @{
 */

/*******Includes***************************************************************/
#include <stdbool.h>
#include "errcodes.h"
#include "device_registers.h"

/*******Definitions************************************************************/
#define DMA_CHANNEL_TRANFER_MAX_SIZE (4095U) /*!< Maximum number of times the DMA channel transmit data */

/*!
 * @brief Transfer type
 */
typedef enum {
    DMA_TRANSFER_MEM2MEM       = 0x00U,      /*!< Transfer from memory to memory */
    DMA_TRANSFER_MEM2PERIPH    = 0x01U,      /*!< Transfer from memory to peripheral */
    DMA_TRANSFER_PERIPH2MEM    = 0x02U,      /*!< Transfer from peripheral to memory */
    DMA_TRANSFER_PERIPH2PERIPH = 0x03U       /*!< Transfer from peripheral to peripheral */
} dma_transfer_type_t;

/*!
 * @brief Transfer data width
 */
typedef enum {
    DMA_TRANSFER_WIDTH_8BIT   = 0x00U,        /*!< Transmission bits width is bytes */
    DMA_TRANSFER_WIDTH_16BIT  = 0x01U,        /*!< Transmission bits width is half word */
    DMA_TRANSFER_WIDTH_32BIT  = 0x02U         /*!< Transmission bits width is word */
} dma_transfer_width_t;

/*!
 * @brief Callback event
 */
typedef enum {
    DMA_EVENT_TRANFER_COMPLETE  = 0x00U,      /*!< DMA tranfer complete */
    DMA_EVENT_TRANFER_ERROR     = 0x01U       /*!< DMA tranfer error */
} dma_event_t;

/*!
 *  @brief Channel interrupt type
 */
typedef enum {
    DMA_CHN_COMPLETE_INT  = 0x01U,                                     /*!< DMA channel complete interrupt */
    DMA_CHN_ERROR_INT     = 0x02U,                                     /*!< DMA channel error interrupt */
    DMA_CHN_ALL_INT       = (DMA_CHN_COMPLETE_INT | DMA_CHN_ERROR_INT) /*!< DMA channel all interrupt */
} dma_channel_int_t;

/*!
 *  @brief Channel status for DMA channel
 */
typedef enum {
    DMA_STATUS_NORMAL       = 0x00U,          /*!< DMA channel normal state */
    DMA_STATUS_COMPLETE     = 0x01U,          /*!< DMA channel transfer complete state */
    DMA_STATUS_ERROR        = 0x02U           /*!< An error occurred in the DMA channel */
} dma_channel_status_t;

/*!
 *  @brief Definition for the DMA channel callback function
 */
typedef void (*dma_callback_t)(uint8_t channel, dma_event_t event, void* param);

/*!
 *  @brief DMA channel configuration structure
 */
typedef struct {
    uint8_t channel;                           /*!< Channel Id */
    uint32_t srcAddress;                       /*!< Memory address pointing to the source data */
    uint32_t destAddress;                      /*!< Memory address pointing to the destination data */

    bool srcAddressIncrement;                  /*!< The source address is incremented after each transfer */
    bool destAddressIncrement;                 /*!< The destination address is incremented after each transfer */

    dma_transfer_width_t srcTransferWidth;     /*!< Source transfer width: 8/16/32 bits  */
    dma_transfer_width_t destTransferWidth;    /*!< Destination transfer width: 8/16/32 bits */

    uint32_t transferSize;                     /*!< Transfer size: 0~4095 */

    dma_transfer_type_t transferType;          /*!< Transfer type: M->M, M->P, P->M, P->P */
    dma_perial_request_t srcPeriphRequest;     /*!< Source peripheral request id */
    dma_perial_request_t destPeriphRequest;    /*!< Destination peripheral request id */

    bool circularModel;                        /*!< Whether to enable cyclic mode */

    bool completeIntEnable;                    /*!< Whether to enable transmission completion interrupt */
    bool errorIntEnable;                       /*!< Whether to enable transmission error interrupt */

    dma_callback_t callback;                   /*!< DMA callback function */
    void* callbackParam;                       /*!< DMA callback function parameters */
} dma_channel_config_t;

/*!
 *  @brief DMA linked list control structure
 */
typedef struct {
    uint32_t transferSize        : 12U;        /*!< Transfer size: 0~4095. */
    uint32_t                     : 6U;
    uint32_t srcTransferWidth    : 3U;         /*!< Source transfer width: 8/16/32 bits  */
    uint32_t destTransferWidth   : 3U;         /*!< Destination transfer width: 8/16/32 bits */
    uint32_t                     : 2U;
    uint32_t srcAddressIncrement : 1U;         /*!< The source address is incremented after each transfer */
    uint32_t destAddressIncrement: 1U;         /*!< The destination address is incremented after each transfer */
    uint32_t                     : 3U;
    uint32_t lliITCEnable        : 1U;         /*!< Whether the flag is set after the current LLI transmission is completed or error occurs */
} dma_linked_list_control_t;

/*!
 *  @brief DMA linked list item structure
 */
typedef struct dma_linked_list_item {
    uint32_t srcAddress;                       /*!< Memory address pointing to the source data */
    uint32_t destAddress;                      /*!< Memory address pointing to the destination data */
    struct dma_linked_list_item* next;         /*!< Points to next linked list item, if null, stop tranversal */
    dma_linked_list_control_t control;         /*!< Channel control parameters */
} dma_linked_list_item_t;

/*!
 *  @brief DMA channel callback structure
 */
typedef struct {
    dma_callback_t callback;                   /*!< DMA callback function */
    void* callbackParam;                       /*!< DMA callback function parameters */
    dma_event_t event;                         /*!< DMA callback event(completion or error) */
} dma_channel_callback_t;

/*******APIs*******************************************************************/
#if defined(__cplusplus)
extern "C" {
#endif

/*!
 * @brief Initializes the DMA module
 *
 * @param[in] chnConfigArray: DMA channel configuration array
 * @param[in] chnCount: DMA channel configuration array size
 * @return None
 */
void DMA_Init(const dma_channel_config_t* chnConfigArray[], uint8_t chnCount);

/*!
 * @brief Deinitializes the DMA module
 *
 * @return None
 */
void DMA_Deinit(void);

/*!
 * @brief Initializes the DMA chanel
 *
 * @param[in] channelConfig: DMA channel configuration
              Note: The source width multiplied by the tranferSize must be an interger multiple of the destination width
 * @return None
 */
void DMA_ChannelInit(const dma_channel_config_t* channelConfig);

/*!
 * @brief Deinitializes the DMA chanel
 *
 * @param[in] channel: DMA channel number
 * @return None.
 */
void DMA_ChannelDeInit(uint8_t channel);

/*!
 * @brief Configures single data transfer with DMA
 *
 * @param[in] channel: DMA channel number
 * @param[in] type: Transfer type(M->M,M->P,P->M)
 * @param[in] srcAddr: Source register address or source memory address
 * @param[in] destAddr: Destination register address or destination memory address
 * @param[in] transferWidth: Transfer data width(8/16/32bits)
 * @param[in] transferSize: Transfer size(0~4095)
 * @param[in] request: Peripheral request id, ignored if transfer type is M->M
 * @return None
 */
void DMA_ConfigSingleTransfer(uint8_t channel,
                              dma_transfer_type_t type,
                              uint32_t srcAddr,
                              uint32_t destAddr,
                              dma_transfer_width_t transferWidth,
                              uint32_t transferSize,
                              dma_perial_request_t request);

/*!
 * @brief Configures the DMA transfer in loop mode
 *
 * @param[in] channel: DMA channel number
 * @param[in] type: Transfer type(M->M,M->P,P->M)
 * @param[in] srcAddr: Source register address or source memory address
 * @param[in] destAddr: Destination register address or destination memory address
 * @param[in] transferWidth: Transfer data width(8/16/32bits)
 * @param[in] transferSize: Transfer size(0~4095)
 * @param[in] request: Peripheral request id, ignored if transfer type is M->M
 * @return None
 */
void DMA_ConfigLoopTransfer(uint8_t channel,
                            dma_transfer_type_t type,
                            uint32_t srcAddr,
                            uint32_t destAddr,
                            dma_transfer_width_t transferWidth,
                            uint32_t transferSize,
                            dma_perial_request_t request);

/*!
 * @brief Configures linked list data transfer with DMA
 *
 * @param[in] channel: DMA channel number.
 * @param[in] type: Transfer type(M->M,M->P,P->M)
 * @param[in] request: Peripheral request id, ignored if transfer type is M->M
 * @param[in] linkedList: Linked list struct
 * @return None
 */
void DMA_ConfigLinkedListTransfer(uint8_t channel,
                                  dma_transfer_type_t type,
                                  dma_perial_request_t request,
                                  const dma_linked_list_item_t* linkedList);

/*!
 * @brief Registers the callback function and the parameter for DMA channel
 *
 * @param[in] channel: DMA channel number
 * @param[in] callback: The pointer to the callback function
 * @param[in] parameter: The pointer to the callback function's parameter
 * @return None
 */
void DMA_InstallCallback(uint8_t channel,
                         dma_callback_t callback,
                         void* parameter);

/*!
 * @brief Disables/Enables the channel interrupt requests
 *
 * @param[in] channel: DMA channel number.
 * @param[in] intType: Interrupt event (error/complete loop)
 * @param[in] enable: Enable (true)/Disable (false) interrupts for the current channel
 * @return None
 */
void DMA_ConfigureInt(uint8_t channel,
                      dma_channel_int_t intType,
                      bool enable);

/*!
 * @brief Start the DMA channel, this function enable DMA channel
 *
 * @param[in] channel: DMA channel number
 * @return None
 */
void DMA_StartChannel(uint8_t channel);

/*!
 * @brief Stop the DMA channel, this function disable DMA channel
 *
 * @param[in] channel: DMA channel number
 * @return None
 */
void DMA_StopChannel(uint8_t channel);

/*!
 * @brief When the transmission is completed, reconfigure the address and tranferSize to transmit again
 *
 * @param[in] channel: DMA channel number
 * @param[in] srcAddr: Source register address or source memory address
 * @param[in] destAddr: Destination register address or destination memory address
 * @param[in] transferSize: Transfer size(0~4095)
 * @return None
 */
void DMA_RestartChannel(uint8_t channel,
                        uint32_t srcAddr,
                        uint32_t destAddr,
                        uint32_t transferSize);

/*!
 * @brief Set DMA tranmission pause or continue
 *
 * @param[in] channel: DMA channel number
 * @param[in] halt: If true, the tranmission is suspended, otherwise, the tranmission continues
 * @return None
 */
void DMA_SetChannelHalt(uint8_t channel, bool halt);

/*!
 * @brief Get DMA channel remain transfered data num
 *
 * @param[in] channel: DMA channel number
 * @return Remain transfered data num
 */
uint32_t DMA_GetRemainingCount(uint8_t channel);

/*!
 * @brief Get DMA channel transfered status(normal/complete/error)
 *
 * @param[in] channel: DMA channel number
 * @return DMA_STATUS_NORMAL: DMA channel normal state
 *         DMA_STATUS_COMPLETE: DMA channel transfer complete state
 *         DMA_STATUS_ERROR: An error occurred in the DMA channel
 */
dma_channel_status_t DMA_GetChannelStatus(uint8_t channel);

/*!
 * @brief Triggers a sw request for the current channel
 *
 * @param[in] request: DMA request source
 * @return None
 */
void DMA_TriggerSwRequest(dma_perial_request_t request);

/*!
 * @brief DMA channel interrupt handler process
 *
 * @param[in] channel: DMA channel number
 * @return None
 */
void DMA_IRQHandlerProcess(uint8_t channel);

#if defined(__cplusplus)
}
#endif

/*! @} */

#endif /* DMA_DRV_H */

/*******EOF********************************************************************/
